﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Spanner
{
    internal interface IVar: ITerm, IHasBaseName
    {
        VarKind Kind { get; set; }

        void WriteInitialValue(Context ctxt); // Always in a JS context, never in an HTML context.
    }

    public abstract class AbstractVar<T> : Expr<T>, IVar
    {
        internal VarKind Kind;

        VarKind IVar.Kind { get { return this.Kind; } set { this.Kind = value; } }

        internal string BaseName;

        string IHasBaseName.BaseName { get { return this.BaseName; } set { this.BaseName = value; } }

        internal ITerm InitialValue;

        IEnumerable<IVar> ITerm.Dependencies() { return this.Dependencies(); }

        override internal IEnumerable<IVar> Dependencies()
        {
            if (InitialValue != null) foreach (var x in InitialValue.Dependencies()) yield return x;
        }

        void ITerm.Write(Context ctxt, JSorHtml what) { this.Write(ctxt, what); }

        override internal void Write(Context ctxt, JSorHtml what)
        {
            ctxt.WriteIVar(what, this);
        }

        void IVar.WriteInitialValue(Context ctxt) { this.WriteInitialValue(ctxt); }

        internal virtual void WriteInitialValue(Context ctxt)
        {
            throw new NotImplementedException();
        }
    }

    internal enum VarKind { ObsVar, ObsVarField, ComputedVar, Var, Param }

    public class ObsVar<T> : AbstractVar<T>
    {
        internal ObsVar() { Kind = VarKind.ObsVar; }

        internal override void WriteInitialValue(Context ctxt)
        {
            var t = typeof(T);

            var ctor =
                ( (t == typeof(int) || t == typeof(short) || t == typeof(long))
                ? "spanner.obsInt"
                : (t == typeof(double) || t == typeof(float) || t == typeof(decimal))
                ? "spanner.obsDouble"
                : (t.IsArray)
                ? "ko.observableArray"
                : "ko.observable"
                );

            ctxt.Write(ctor);
            ctxt.Write("(");
            ctxt.WriteTerm((ITerm)InitialValue, JSorHtml.JS);
            ctxt.Write(")");
        }
    }

    /// <summary>
    /// This type handles the case where a POCO field has type ObsVar.
    /// We don't want to use, say, FieldExpr(ObsVarRef) for that because
    /// then we'd have to use explicit Derefs all over the place.  Ugh!
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObsVarField<T> : ObsVar<T>, IObsVarField
    {
        internal ObsVarField() { Kind = VarKind.ObsVarField; }

        internal ITerm X;
        internal string Fields;

        void IObsVarField.Write(Context ctxt, JSorHtml what)
        {
            X.Write(ctxt, what);
            ctxt.WritePlain(Fields);
        }
    }

    internal interface IObsVarField
    {
        void Write(Context ctxt, JSorHtml what);
    }

    public class ComputedVar<T> : AbstractVar<T>
    {
        internal ComputedVar() { Kind = VarKind.ComputedVar; }

        internal override void WriteInitialValue(Context ctxt)
        {
            ctxt.WriteLine("ko.computed(function () {{");
            ctxt.Indentation++;
            ctxt.WriteIndentation();
            ctxt.Write("return ");
            ctxt.WriteTerm((ITerm)InitialValue, JSorHtml.JS);
            ctxt.WriteLine(";");
            ctxt.Indentation--;
            ctxt.WriteIndentation();
            ctxt.Write("}})");
        }
    }

    public class Var<T> : AbstractVar<T>
    {
        internal Var() { Kind = VarKind.Var; }

        internal override void WriteInitialValue(Context ctxt)
        {
            ctxt.WriteTerm((ITerm)InitialValue, JSorHtml.JS);
        }

        internal override void Write(Context ctxt, JSorHtml what)
        {
            ctxt.WriteIVar(what, this, deref: false);
        }
    }

    public class Param<T> : AbstractVar<T>
    {
        internal Param() { Kind = VarKind.Param; }

        internal override void Write(Context ctxt, JSorHtml what)
        {
            ctxt.WriteIVar(what, this, deref: false);
        }
    }

    /// <summary>
    /// We use this in places where a parameter is required, but is never given a value.
    /// </summary>
    public sealed class NoType { }

    /// <summary>
    /// We use this in places where a parameter is required, but is never given a value that
    /// fits into Spanner's type system.
    /// </summary>
    public sealed class DummyType { }

    // XXX Add Procs with more params.

    /// <summary, Act>
    /// These are just used to provide type information.
    /// </summary>
    public interface IFn { }; // This is used to identify Fn classes as a family.
    public class Fn<T> : IFn { }
    public class Fn<Ta, T> : IFn { }
    public class Fn<Ta, Tb, T> : IFn { }
    public class Fn<Ta, Tb, Tc, T> : IFn { }
    public class Fn<Ta, Tb, Tc, Td, T> : IFn { }

}
